﻿namespace Atomic.Messages
{
    using System;
    using System.Text;
    using System.Collections;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    using Atomic.Logging;

    /// <summary>
    /// 默认消息总线。
    /// </summary>
    public class DefaultMessageBus : IMessageBus
    {
        /// <summary>
        /// 委托消息处理程序集合。
        /// </summary>
        private readonly Func<IEnumerable<IMessageHandler>> _messageHandlers;

        /// <summary>
        /// 接口方法缓存。
        /// </summary>
        private static readonly ConcurrentDictionary<string, MethodInfo> _interfaceMethodsCache = new ConcurrentDictionary<string, MethodInfo>();

        /// <summary>
        /// 初始化默认消息总线。
        /// </summary>
        /// <param name="messageHandlers">消息处理程序集合。</param>
        public DefaultMessageBus(Func<IEnumerable<IMessageHandler>> messageHandlers)
        {
            this._messageHandlers = messageHandlers;

            this.Logger = LoggerFactory.Current.Create();
        }

        /// <summary>
        /// 日志工具。
        /// </summary>
        protected ILogger Logger { get; private set; }

        /// <summary>
        /// 发送事件通知。
        /// </summary>
        /// <remarks>interfaceName + "." + methodName, data</remarks>
        /// <param name="messageName">消息名称（接口名.方法名）</param>
        /// <param name="eventData">参数字典（Key：参数名,Value：参数值）</param>
        /// <returns>操作结果集合。</returns>
        public IEnumerable Notify(string messageName, IDictionary<string, object> eventData)
        {
            return NotifyHandlers(messageName, eventData).ToArray();
        }

        /// <summary>
        /// 通知处理程序。
        /// </summary>
        /// <param name="messageName">消息信息。</param>
        /// <param name="eventData">事件数据。</param>
        /// <returns>返回值。</returns>
        private IEnumerable<object> NotifyHandlers(string messageName, IDictionary<string, object> eventData)
        {
            string[] parameters = messageName.Split('.');
            if (parameters.Length != 2)
            {
                throw new ArgumentException("{0} 参数格式不正确。");
            }
            string interfaceName = parameters[0];
            string methodName = parameters[1];

            var messageHandlers = _messageHandlers();

            foreach (var messageHandler in messageHandlers)
            {
                IEnumerable returnValue;
                if (TryNotifyHandler(messageHandler, messageName, interfaceName, methodName, eventData, out returnValue))
                {
                    if (returnValue != null)
                    {
                        foreach (var value in returnValue)
                        {
                            yield return value;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 通知处理程序。
        /// </summary>
        /// <param name="messageHandler">消息处理程序。</param>
        /// <param name="messageName">消息名称。</param>
        /// <param name="interfaceName">接口类型名称。</param>
        /// <param name="methodName">方法名。</param>
        /// <param name="eventData">方法参数字典集合。</param>
        /// <param name="returnValue">返回值。</param>
        /// <returns>是否成功。</returns>
        private bool TryNotifyHandler(
            IMessageHandler messageHandler, 
            string messageName, 
            string interfaceName, 
            string methodName, 
            IDictionary<string, object> eventData, 
            out IEnumerable returnValue)
        {
            try
            {
                return TryInvoke(messageHandler, interfaceName, methodName, eventData, out returnValue);
            }
            catch (Exception exception)
            {
                StringBuilder text = new StringBuilder();

                text.AppendLine("执行 IMessageBus.Notify 方法。");
                text.AppendLine("消息名称：" + messageName);
                text.AppendLine("消息参数：");

                if(eventData!=null && eventData.Count > 0)
                {
                    foreach(var data in eventData)
                    {
                        text.Append(data.Key);
                        text.Append(":");
                        text.AppendLine(data.Value.ToString());
                    }
                }

                text.AppendLine("错误信息：" + exception.Message);

                this.Logger.LogError(text.ToString(), exception);

                returnValue = null;
                return false;
            }
        }

        /// <summary>
        /// 调用方法。
        /// </summary>
        /// <param name="messageHandler">消息处理程序。</param>
        /// <param name="interfaceName">接口名称。</param>
        /// <param name="methodName">方法名。</param>
        /// <param name="arguments">方法参数字典集合。</param>
        /// <param name="returnValue">返回值。</param>
        /// <returns>是否成功。</returns>
        private static bool TryInvoke(
            IMessageHandler messageHandler, string interfaceName, string methodName, IDictionary<string, object> arguments, out IEnumerable returnValue)
        {
            Type type = messageHandler.GetType();
            foreach (var interfaceType in type.GetInterfaces())
            {
                if (String.Equals(interfaceType.Name, interfaceName, StringComparison.OrdinalIgnoreCase))
                {
                    return TryInvokeMethod(messageHandler, interfaceType, methodName, arguments, out returnValue);
                }
            }
            returnValue = null;
            return false;
        }

        /// <summary>
        /// 调用方法。
        /// </summary>
        /// <param name="messageHandler">消息处理程序。</param>
        /// <param name="interfaceType">接口类型。</param>
        /// <param name="methodName">方法名。</param>
        /// <param name="arguments">方法参数字典集合。</param>
        /// <param name="returnValue">返回值。</param>
        /// <returns>是否成功。</returns>
        private static bool TryInvokeMethod(
            IMessageHandler messageHandler, Type interfaceType, string methodName, IDictionary<string, object> arguments, out IEnumerable returnValue)
        {
            MethodInfo method = _interfaceMethodsCache.GetOrAdd(
                String.Concat(messageHandler.GetType().Name + "_" + interfaceType.Name, "_", methodName, "_", String.Join("_", arguments.Keys)),
                GetMatchingMethod(messageHandler, interfaceType, methodName, arguments));

            if (method != null)
            {
                var parameters = new List<object>();
                foreach (var methodParameter in method.GetParameters())
                {
                    parameters.Add(arguments[methodParameter.Name]);
                }
                var result = method.Invoke(messageHandler, parameters.ToArray());
                returnValue = result as IEnumerable;
                if (returnValue == null && result != null)
                    returnValue = new[] { result };
                return true;
            }
            returnValue = null;
            return false;
        }

        /// <summary>
        /// 获得方法信息。
        /// </summary>
        /// <param name="messageHandler">消息处理程序。</param>
        /// <param name="interfaceType">接口类型。</param>
        /// <param name="methodName">方法名。</param>
        /// <param name="arguments">方法参数字典集合。</param>
        /// <returns>方法信息。</returns>
        private static MethodInfo GetMatchingMethod(IMessageHandler messageHandler, Type interfaceType, string methodName, IDictionary<string, object> arguments)
        {
            var allMethods = new List<MethodInfo>(interfaceType.GetMethods());
            var candidates = new List<MethodInfo>(allMethods);

            foreach (var method in allMethods)
            {
                if (String.Equals(method.Name, methodName, StringComparison.OrdinalIgnoreCase))
                {
                    ParameterInfo[] parameterInfos = method.GetParameters();
                    foreach (var parameter in parameterInfos)
                    {
                        if (!arguments.ContainsKey(parameter.Name))
                        {
                            candidates.Remove(method);
                            break;
                        }
                    }
                }
                else
                {
                    candidates.Remove(method);
                }
            }

            if (candidates.Count != 0)
            {
                return candidates.OrderBy(x => x.GetParameters().Length).Last();
            }

            return null;
        }
    }
}